export class CurveTimeline {
    public static LINEAR = 0;
    public static STEPPED = 1;
    public static BEZIER = 2;
    public static BEZIER_SIZE = 10 * 2 - 1;
    public curves: any[] = null;
    public constructor(frameCount: number) {
        if (frameCount <= 0) {
            cc.error("frameCount must be > 0: " + frameCount);
        }
        let len = (frameCount - 1) * CurveTimeline.BEZIER_SIZE;
        this.curves = [];
        for (let i = 0; i < len; i++) {
            this.curves.push(0);
        }
    }

    public getFrameCount() {
        return this.curves.length / CurveTimeline.BEZIER_SIZE + 1;
    }

    public setLinear(frameIndex: number) {
        this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.LINEAR;
    }

    public setStepped(frameIndex: number) {
        this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.STEPPED;
    }

    public getCurveType(frameIndex: number) {
        var index = frameIndex * CurveTimeline.BEZIER_SIZE;
        if (index == this.curves.length)
            return CurveTimeline.LINEAR;
        var type = this.curves[index];
        if (type == CurveTimeline.LINEAR)
            return CurveTimeline.LINEAR;
        if (type == CurveTimeline.STEPPED)
            return CurveTimeline.STEPPED;
        return CurveTimeline.BEZIER;
    }

    public setCurve(frameIndex: number, cx1: number, cy1: number, cx2: number, cy2: number) {
        let tmpx = (-cx1 * 2 + cx2) * 0.03, tmpy = (-cy1 * 2 + cy2) * 0.03;
        let dddfx = ((cx1 - cx2) * 3 + 1) * 0.006, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006;
        let ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
        let dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667, dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667;
        let i = frameIndex * CurveTimeline.BEZIER_SIZE;
        let curves = this.curves;
        curves[i++] = CurveTimeline.BEZIER;
        let x = dfx, y = dfy;
        for (let n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) {
            curves[i] = x;
            curves[i + 1] = y;
            dfx += ddfx;
            dfy += ddfy;
            ddfx += dddfx;
            ddfy += dddfy;
            x += dfx;
            y += dfy;
        }
    }

    public getCurvePercent(frameIndex: number, percent: number) {
        percent = cc.misc.clampf(percent, 0, 1);
        let curves = this.curves;
        let i = frameIndex * CurveTimeline.BEZIER_SIZE;
        let type = curves[i];
        if (type == CurveTimeline.LINEAR)
            return  percent;
        if (type == CurveTimeline.STEPPED)
            return 0;
        i++;
        let x = 0;
        for (let start = i, n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) {
            x = curves[i];
            if (x >= percent) {
                let prevX = void 0, prevY = void 0;
                if (i == start) {
                    prevX = 0;
                    prevY = 0;
                }
                else {
                    prevX = curves[i - 2];
                    prevY = curves[i - 1];
                }
                return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
            }
        }
        let y = curves[i - 1];
        return y + (1 - y) * (percent - x) / (1 - x);
    }
}

export class DeformTimeline extends CurveTimeline {
    public frames: any[] = null;
    public frameVertices: any[] = null;
    private _sprite: cc.Sprite = null;
    public constructor(frameCount: number, sprite: cc.Sprite) {
        super(frameCount);
        this.frames = [];
        this._sprite = sprite;
        for (let i = 0; i < frameCount; i++) {
            this.curves.push(0);
        }
        this.frameVertices = new Array(frameCount);
    }

    public getPropertyId() {
        return 0;
    }

    public setFrame(frameIndex: number, time: number, vertices: number[]) {
        this.frames[frameIndex] = time;
        this.frameVertices[frameIndex] = vertices;
    }

    public apply(time, alpha = 1, blend) {
        let frameVertices = this.frameVertices;
        let vertexCount = frameVertices[0].length;
        let frames = this.frames;
        let frame = this.binarySearch(frames, time);
        let frameTime = frames[frame];
        let prevVertices = frameVertices[frame - 1];
        let nextVertices = frameVertices[frame];
        let percent = this.getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
        let x = 0;
        let y = 0;
        let nx = 0;
        let ny = 0;
        for (let i = 0, len = vertexCount/2; i < len; i++) {
            x = prevVertices[2 * i];
            y = prevVertices[2 * i + 1];
            nx = nextVertices[2 * i];
            ny = nextVertices[2 * i + 1]
            this._sprite.spriteFrame.vertices.x[i] = x + (nx - x) * percent;
            this._sprite.spriteFrame.vertices.y[i] = y + (ny - y) * percent;
        }
        this._sprite.setVertsDirty();
    }

    private binarySearch(values: number[], target: number, step: number = 1) {
        let low = 0;
        let high = values.length / step - 2;
        if (high == 0) {
            return step;
        }
        let current = high >>> 1;
        while (true) {
            if (values[(current + 1) * step] <= target)
                low = current + 1;
            else
                high = current;
            if (low == high)
                return (low + 1) * step;
            current = (low + high) >>> 1;
        }
    }
}